package ro.upt.ac.cstaicu.instrumentation.logging;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Stack;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import ro.upt.ac.cstaicu.analyze.Entity;
import ro.upt.ac.cstaicu.execution.Executor;
import ro.upt.ac.cstaicu.execution.TestResult;
import ro.upt.ac.cstaicu.ui.EclipseConnector;


public class XMLGlobalLogger implements GlobalLogger {

	private static XMLGlobalLogger instance = null;

	private Document currentDocument;
	private Element rootElement;
	private StreamResult outputFile;

	private Element currentParent = null;

	private Stack<Element> parents = new Stack<Element>();
	private String currentClass = "";
	
	private HashMap<Integer, ArrayList<Element>> targetLabels = new HashMap<Integer, ArrayList<Element>>();
	
	// :)) brute force
	private ArrayList<String> classSourceCode;
	private Element lastTest = null;

	private ArrayList<TestResult> results = new ArrayList<TestResult>();

	private XMLGlobalLogger() {
	}

	public static XMLGlobalLogger getInstance() {
		if (instance == null)
			instance = new XMLGlobalLogger();
		return instance;
	}

	public void testStarted(String className, String methodName) {
		Element testElement = currentDocument.createElement("test");
		testElement.setAttribute("className", className);
		testElement.setAttribute("methoEdName", methodName);
		rootElement.appendChild(testElement);
		currentParent = testElement;
		lastTest = testElement;
	}

	// TODO throw Illegal state exception
	public void startLogging(String fileName) {

		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory
					.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();
			currentDocument = builder.newDocument();
			rootElement = currentDocument.createElement("report");
			rootElement.setAttribute("date", new Date().toString());

			FileOutputStream out = new FileOutputStream(fileName);
			outputFile = new StreamResult(out);
		} catch (ParserConfigurationException e) {
			System.out.println(e);
		} catch (FileNotFoundException e) {
			System.out.println(e);
		}

	}

	public void logPredicate(int predicateID, int opcode, int lineNo, boolean value) {

		if (currentDocument != null) {
			Element predicateElement = currentDocument
					.createElement("predicate");

//			addWrappedTextElement(predicateElement, "lineNumber", lineNo);
			addWrappedTextElement(predicateElement, "ID", predicateID);
			addWrappedTextElement(predicateElement, "value", value);
//			addWrappedTextElement(predicateElement, "opcode", opcode);
//			addWrappedTextElement(predicateElement, "scLine", classSourceCode
//					.get(lineNo - 1));

			currentParent.appendChild(predicateElement);
		}
	}

	public void logFunctionCall(String className, String methodName,
			ArrayList<MethodParameter> parameters) {

		if (currentDocument != null) {

			// TODO refine this piece of code for recursive calls
			Element functionElement = currentDocument
					.createElement("methodCall");
			functionElement.setAttribute("className", className);
			functionElement.setAttribute("mEethodName", methodName);

			if (parameters != null && parameters.size() != 0)
				for (int i = 0; i < parameters.size(); i++)
					addWrappedTextElement(functionElement,
							parameters.get(i).name, parameters.get(i).value);

			currentParent.appendChild(functionElement);
			parents.push(currentParent);
			currentParent = functionElement;

			// TODO de scos scarbosenia asta
			if (!currentClass.equals(className)) {
				this.currentClass = className;
				String pathToClass = EclipseConnector.getPathToSourceCode()
						+ className.replaceAll("\\.", "\\" + File.separator)
						+ ".java";
				try {
					BufferedReader reader = new BufferedReader(
							new InputStreamReader(new FileInputStream(
									pathToClass)));
					String line;
					classSourceCode = new ArrayList<String>();
					while ((line = reader.readLine()) != null)
						classSourceCode.add(line);

				} catch (IOException e) {
					System.out.println(e);
				}
			}
		}
	}

	public void functionEnded() {
		currentParent = parents.pop();
	}

	public void setRunningTime(long time) {
		rootElement.setAttribute("runningMilis", new Long(time).toString());
	}

	public void setResult(TestResult result) {
		results.add(result);
		String res = "";
		if (result.result == Executor.TEST_PASSED)	
			 res = "PASSED";
		else 
			res = "FAILED";
		lastTest.setAttribute("result", res);
	}

	public Collection<Entity> endLogging() {
		try {
			currentDocument.appendChild(rootElement);
			TransformerFactory factory = TransformerFactory.newInstance();
			Transformer tr = factory.newTransformer();
			DOMSource source = new DOMSource(currentDocument);
			tr.transform(source, outputFile);
			currentDocument = null;
		} catch (TransformerException e) {
			System.out.println(e);
		}
		//TODO sanitize this
		return null;
	}

	private Element addWrappedTextElement(Element parent, String elementName,
			Object value) {

		Element newElement = currentDocument.createElement(elementName);
		newElement
				.appendChild(currentDocument.createTextNode(value.toString()));
		parent.appendChild(newElement);

		return newElement;

	}
	
	public void resolveLabel(int labelHash, int offset) {
		System.out.println("RESOLVED " + labelHash + " " + offset);
		if (targetLabels.containsKey(labelHash))
			for (Element e : targetLabels.get(labelHash))
				addWrappedTextElement(e, "targetLine", offset);
	}
	
	public Collection<TestResult> getResults() {
		return results;
	}

	@Override
	public void addAllEntities(Collection<Entity> tracedPredicates) {
		
	}
	
	@Override
	public void clearPredicates() {
		
	}

}
